namespace MetaProperties.SynchronizationContexts
{
    using System;
    using System.Collections.Generic;
    using System.Security.Permissions;
    using System.Threading;

    public enum CallPriority
    {
        Low,
        Normal,
        High
    }

    /// <summary>
    /// Based on code from: http://msdn.microsoft.com/en-us/magazine/cc163321.aspx.
    /// </summary>
    [SecurityPermission(SecurityAction.Demand, ControlThread = true)]
    public class PriorityThreadPoolSynchronizationContext : ThreadPoolSynchronizationContext
    {
        private const string SlotName = "CallPriority";

        private readonly Queue<WorkItem> highPriorityItemQueue;

        private readonly Queue<WorkItem> lowPriorityItemQueue;

        private readonly Queue<WorkItem> normalPriorityItemQueue;

        public PriorityThreadPoolSynchronizationContext(uint poolSize) 
            : this(poolSize, "PTPSC")
        {
        }

        public PriorityThreadPoolSynchronizationContext(uint poolSize, string poolName)
            : base(poolSize, poolName)
        {
            this.lowPriorityItemQueue = new Queue<WorkItem>();
            this.normalPriorityItemQueue = new Queue<WorkItem>();
            this.highPriorityItemQueue = new Queue<WorkItem>();
        }

        public static CallPriority Priority
        {
            get
            {
                LocalDataStoreSlot slot = Thread.GetNamedDataSlot(SlotName);
                object data = Thread.GetData(slot);
                if (data == null)
                {
                    return CallPriority.Normal;
                }

                return (CallPriority)data;
            }

            set
            {
                LocalDataStoreSlot slot = Thread.GetNamedDataSlot(SlotName);
                Thread.SetData(slot, value);
            }
        }

        protected override bool QueueEmpty
        {
            get
            {
                lock (this.lowPriorityItemQueue)
                {
                    if (this.lowPriorityItemQueue.Count > 0)
                    {
                        return false;
                    }
                }

                lock (this.normalPriorityItemQueue)
                {
                    if (this.normalPriorityItemQueue.Count > 0)
                    {
                        return false;
                    }
                }

                lock (this.highPriorityItemQueue)
                {
                    if (this.highPriorityItemQueue.Count > 0)
                    {
                        return false;
                    }
                }

                return true;
            }
        }

        internal override WorkItem GetNext()
        {
            this.ItemAdded.WaitOne();
            lock (this.highPriorityItemQueue)
            {
                if (this.highPriorityItemQueue.Count >= 1)
                {
                    return this.highPriorityItemQueue.Dequeue();
                }
            }

            lock (this.normalPriorityItemQueue)
            {
                if (this.normalPriorityItemQueue.Count >= 1)
                {
                    return this.normalPriorityItemQueue.Dequeue();
                }
            }

            lock (this.lowPriorityItemQueue)
            {
                if (this.lowPriorityItemQueue.Count >= 1)
                {
                    return this.lowPriorityItemQueue.Dequeue();
                }
            }

            return null;
        }

        internal override void QueueWorkItem(WorkItem workItem)
        {
            CallPriority priority = PriorityThreadPoolSynchronizationContext.Priority;

            switch (priority)
            {
                case CallPriority.Low:
                {
                    lock (this.lowPriorityItemQueue)
                    {
                        this.lowPriorityItemQueue.Enqueue(workItem);
                        this.ItemAdded.Release();
                        return;
                    }
                }

                case CallPriority.Normal:
                {
                    lock (this.normalPriorityItemQueue)
                    {
                        this.normalPriorityItemQueue.Enqueue(workItem);
                        this.ItemAdded.Release();
                        return;
                    }
                }

                case CallPriority.High:
                {
                    lock (this.highPriorityItemQueue)
                    {
                        this.highPriorityItemQueue.Enqueue(workItem);
                        this.ItemAdded.Release();
                        return;
                    }
                }

                default:
                {
                    throw new InvalidOperationException("Unknown priority value: " + priority);
                }
            }
        }
    }
}